package test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.hibernate.HibernateException;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import trip.*;
import dao.*;

public class TripDAOTest extends Assert {

	private Person_tripDAO ptD = Factory.getInstance().getPerson_tripDAO();
	private PersonDAO pD = Factory.getInstance().getPersonDAO();
	private TripDAO tD = Factory.getInstance().getTripDAO();
	private RouteDAO rD = Factory.getInstance().getRouteDAO();
	private StopDAO sD = Factory.getInstance().getStopDAO();
	private CompanyDAO cD = Factory.getInstance().getCompanyDAO();
	private Time_tableDAO ttD = Factory.getInstance().getTime_tableDAO();
	private Route_stopsDAO rsD = Factory.getInstance().getRoute_stopsDAO();
	
	private boolean contains(List<Company> list, Company obj) {
		if (obj == null)
			return false;
		if (list == null || list.isEmpty())
			return true;
		for(Company tmpObj : list) {
			if (tmpObj.equals(obj))
				return true;
		}
		return false;
	}
	
	private boolean contains(List<Route> list, Route obj) {
		if (obj == null)
			return false;
		if (list == null || list.isEmpty())
			return false;
		for(Route tmpObj : list) {
			if (tmpObj.equals(obj))
				return true;
		}
		return false;
	}
	
	@DataProvider
	public Object[][] tripData() {
		return new Object[][] {
				{null, null, false},
				{null, 0, false},
				{0, null, false},
				{0, 1, true},
				{-1, -1, true},
				{10, 10, true}
		};
	}
	
	@Test(dataProvider="tripData")
	public void dataTest(Integer number, Integer seats, boolean expected) {
		Company company = null;
		Route route = null;
		Trip trip = null;
		try {
			Integer count = tD.getAll().size();
			assertNotNull(count);
			company = new Company(1, "x");
			route = new Route(1, 10, "x", true);
			trip = new Trip(1, number, route, company, seats);
			cD.create(company);
			rD.create(route);
			tD.create(trip);
			Trip tmpTrip = tD.getById(trip.getId());
			assertNotNull(tmpTrip);
			assertEquals(tmpTrip.getId(), trip.getId());
			assertEquals(tmpTrip.getNumber(), trip.getNumber());
			assertTrue(tmpTrip.getRoute().equals(trip.getRoute()));
			assertTrue(tmpTrip.getCompany().equals(trip.getCompany()));
			assertEquals(tmpTrip.getSeats(), trip.getSeats());
			trip.setSeats(trip.getSeats() + 7);
			tD.update(trip);
			tmpTrip = tD.getById(trip.getId());
			assertNotNull(tmpTrip);
			assertEquals(tmpTrip.getId(), trip.getId());
			assertEquals(tmpTrip.getNumber(), trip.getNumber());
			assertTrue(tmpTrip.getRoute().equals(trip.getRoute()));
			assertTrue(tmpTrip.getCompany().equals(trip.getCompany()));
			assertEquals(tmpTrip.getSeats(), trip.getSeats());
			cD.delete(company);
			rD.delete(route);
			tmpTrip = tD.getById(trip.getId());
			assertNull(tmpTrip);
			assertEquals(tD.getAll().size(), count.intValue());
			assertTrue(expected);
		} catch (HibernateException ex) {
			assertFalse(expected);
			if (company != null)
				cD.delete(company);
			if (route != null)
				rD.delete(route);
		}
	}
	
	@Test
	public void getByFilterCompanies() {
		List<Trip> trips = tD.getAll();
		assertNotNull(trips);
		List<Stop> stops = sD.getAll();
		assertNotNull(stops);
		List<Company> comps = cD.getAll();
		assertNotNull(comps);
		for(Stop st1 : stops) {
			for(Stop st2 : stops) {
				for(int i = -1; i < comps.size(); i++){
					List<Company> choice = new ArrayList<Company>();
					List<Route> rByS = rD.getByStops(st1, st2);
					assertNotNull(rByS);
					for(int j = 0; j < i + 1; j++)
						choice.add(comps.get(j));
					List<Trip> tByFC = tD.getByFilters(null, null, st1, st2, null, null, null, null, null, null, choice);
					assertNotNull(tByFC);
					if (tByFC.size() != 0) {
						for(Trip trip : tByFC) {
							assertTrue(contains(choice, trip.getCompany()));
						}
					}
					else {
						for(Trip trip : trips)
							assertFalse(contains(rByS, trip.getRoute()) && contains(choice, trip.getCompany()));
					}
				}
			}
		}
	}
	
	@DataProvider
	public Object[][] seatsData() {
		return new Object[][] {
				{null},
				{1},
				{2},
				{3},
				{100},
				{-100},
				{0}
		};
	}
	
	@Test(dataProvider = "seatsData")
	public void getByFilterSeats(Integer seats) {
		List<Trip> trips = tD.getAll();
		assertNotNull(trips);
		List<Stop> stops = sD.getAll();
		assertNotNull(stops);
		List<Time_table> tts = ttD.getAll();
		assertNotNull(tts);
		List<Route_stops> rss = rsD.getAll();
		assertNotNull(rss);
		for(Stop st1 : stops) {
			for(Stop st2 : stops) {
				List<Route> rByS = rD.getByStops(st1, st2);
				assertNotNull(rByS);
				List<Trip> tByFS = tD.getByFilters(null, null, st1, st2, seats, null, null, null, null, null, null);
				assertNotNull(tByFS);
				if (tByFS.size() != 0) {
					for(Trip trip : tByFS) {
						Integer st1num = null, st2num = null;
						for(Route_stops rs : rss) {
							if (rs.getRoute().equals(trip.getRoute())) {
								if (rs.getStop().equals(st1))
									st1num = rs.getStopNumber();
								else 
									if (rs.getStop().equals(st2))
										st2num = rs.getStopNumber();
							}
						}
						assertNotNull(st1num);
						assertNotNull(st2num);
						assertTrue(st1num < st2num);
						Integer vacant_seats = Integer.MAX_VALUE;
						for(Time_table tt : tts) {
							if (tt.getTrip().equals(trip) && vacant_seats > tt.getVacantSeats() &&
								tt.getStop_number() >= st1num && tt.getStop_number() < st2num)
								vacant_seats = tt.getVacantSeats();
						}
						assertNotEquals(vacant_seats, Integer.MAX_VALUE);
						assertTrue(seats == null || seats <= vacant_seats);
					}
				}
				else {
					for(Trip trip : trips) {
						if (!contains(rByS, trip.getRoute()))
							continue;
						Integer st1num = null, st2num = null;
						for(Route_stops rs : rss) {
							if (rs.getRoute().equals(trip.getRoute())) {
								if (rs.getStop().equals(st1))
									st1num = rs.getStopNumber();
								else 
									if (rs.getStop().equals(st2))
										st2num = rs.getStopNumber();
							}
						}
						assertNotNull(st1num);
						assertNotNull(st1num);
						assertTrue(st1num < st2num);
						Integer vacant_seats = Integer.MAX_VALUE;
						for(Time_table tt : tts) {
							if (tt.getTrip().equals(trip) && vacant_seats > tt.getVacantSeats() &&
								tt.getStop_number() >= st1num && tt.getStop_number() < st2num)
								vacant_seats = tt.getVacantSeats();
						}
						assertNotEquals(vacant_seats, Integer.MAX_VALUE);
						assertNotNull(seats);
						assertTrue(seats < 0 || seats > vacant_seats);
					}
				}
			}
		}
	}
	
	@DataProvider
	public Object[][] priceData() {
		return new Object[][] {
				{null, null},
				{1000, null},
				{null, 2000},
				{-100, 200},
				{100, -200},
				{0, 100000},
				{100000, 200000},
				{0, 0},
				{10000, 500}
		};
	}
	
	@Test(dataProvider = "priceData")
	public void getByFilterPrice(Integer minprice, Integer maxprice) {
		List<Trip> trips = tD.getAll();
		assertNotNull(trips);
		List<Stop> stops = sD.getAll();
		assertNotNull(stops);
		List<Time_table> tts = ttD.getAll();
		assertNotNull(tts);
		List<Route_stops> rss = rsD.getAll();
		assertNotNull(rss);
		for(Stop st1 : stops) {
			for(Stop st2 : stops) {
				List<Route> rByS = rD.getByStops(st1, st2);
				assertNotNull(rByS);
				List<Trip> tByFS = tD.getByFilters(null, null, st1, st2, null, minprice, maxprice, null, null, null, null);
				assertNotNull(tByFS);
				if (minprice == null)
					minprice = 0;
				if (maxprice == null)
					maxprice = Integer.MAX_VALUE;
				if (tByFS.size() != 0) {
					for(Trip trip : tByFS) {
						Integer st1num = null, st2num = null;
						for(Route_stops rs : rss) {
							if (rs.getRoute().equals(trip.getRoute())) {
								if (rs.getStop().equals(st1))
									st1num = rs.getStopNumber();
								else 
									if (rs.getStop().equals(st2))
										st2num = rs.getStopNumber();
							}
						}
						assertNotNull(st1num);
						assertNotNull(st2num);
						assertTrue(st1num < st2num);
						Integer sum = 0;
						for(Time_table tt : tts) {
							if (tt.getTrip().equals(trip) &&
								tt.getStop_number() > st1num && tt.getStop_number() <= st2num) {
								sum += tt.getPrice();
							}
						}
						assertFalse(sum < 0);
						assertTrue(sum >= minprice && sum <= maxprice);
					}
				}
				else {
					for(Trip trip : trips) {
						if (!contains(rByS, trip.getRoute()))
							continue;
						Integer st1num = null, st2num = null;
						for(Route_stops rs : rss) {
							if (rs.getRoute().equals(trip.getRoute())) {
								if (rs.getStop().equals(st1))
									st1num = rs.getStopNumber();
								else 
									if (rs.getStop().equals(st2))
										st2num = rs.getStopNumber();
							}
						}
						assertNotNull(st1num);
						assertNotNull(st1num);
						assertTrue(st1num < st2num);
						Integer sum = 0;
						for(Time_table tt : tts) {
							if (tt.getTrip().equals(trip) &&
								tt.getStop_number() > st1num && tt.getStop_number() <= st2num) {
								sum += tt.getPrice();
							}
						}
						assertTrue(minprice < 0 || sum < minprice || 
								   maxprice < 0 || sum > maxprice||
								   minprice > maxprice);
					}
				}
			}
		}
	}
	
	@DataProvider
	public Object[][] timeData() {
		return new Object[][] {
				{null, null},
				{"2009-01-12 18:00:00", null},
				{null, "2015-01-12 18:00:00"},
				{"2009-01-12 18:00:00", "2015-01-12 18:00:00"},
				{"2015-01-12 18:00:00", "2009-01-12 18:00:00"}
		};
	}
	
	@Test(dataProvider = "timeData")
	public void getByFilterDate(String in, String out) {
		SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			Date inT = (in == null) ? null : parser.parse(in),
				 outT = (out == null) ? null : parser.parse(out);
			List<Trip> trips = tD.getAll();
			assertNotNull(trips);
			List<Stop> stops = sD.getAll();
			assertNotNull(stops);
			List<Time_table> tts = ttD.getAll();
			assertNotNull(tts);
			List<Route_stops> rss = rsD.getAll();
			assertNotNull(rss);
			for(Stop st1 : stops) {
				for(Stop st2 : stops) {
					List<Route> rByS = rD.getByStops(st1, st2);
					assertNotNull(rByS);
					List<Trip> tByFD = tD.getByFilters(inT, outT, st1, st2, null, null, null, null, null, null, null);
					assertNotNull(tByFD);
					if (tByFD.size() != 0) {
						for(Trip trip : tByFD) {
							Integer st1num = null, st2num = null;
							for(Route_stops rs : rss) {
								if (rs.getRoute().equals(trip.getRoute())) {
									if (rs.getStop().equals(st1))
										st1num = rs.getStopNumber();
									else 
										if (rs.getStop().equals(st2))
											st2num = rs.getStopNumber();
								}
							}
							assertNotNull(st1num);
							assertNotNull(st2num);
							assertTrue(st1num < st2num);
							Date arr = null, dep = null;
							for(Time_table tt : tts) {
								if (tt.getTrip().equals(trip)) {
									if (tt.getStop_number() == st1num)
										arr = tt.getDeparture();
									else
										if (tt.getStop_number() == st2num)
											dep = tt.getArrival();
								}
							}
							assertNotNull(arr);
							assertNotNull(dep);
							if (inT != null)
								inT.setTime(inT.getTime() - 1);
							if (outT != null)
								outT.setTime(outT.getTime() + 1);
							assertTrue(inT == null || arr.after(inT));
							assertTrue(outT == null || dep.before(outT));
						}
					}
					else {
						for(Trip trip : trips) {
							if (!contains(rByS, trip.getRoute()))
								continue;
							Integer st1num = null, st2num = null;
							for(Route_stops rs : rss) {
								if (rs.getRoute().equals(trip.getRoute())) {
									if (rs.getStop().equals(st1))
										st1num = rs.getStopNumber();
									else 
										if (rs.getStop().equals(st2))
											st2num = rs.getStopNumber();
								}
							}
							assertNotNull(st1num);
							assertNotNull(st2num);
							assertTrue(st1num < st2num);
							Date arr = null, dep = null;
							for(Time_table tt : tts) {
								if (tt.getTrip().equals(trip)) {
									if (tt.getStop_number() == st1num)
										arr = tt.getDeparture();
									else
										if (tt.getStop_number() == st2num)
											dep = tt.getArrival();
								}
							}
							assertTrue(((inT == null || arr.before(inT)) | (outT == null || dep.after(outT))) ||
										 inT.after(outT));
						}
					}
				}
			}
		} catch(ParseException ex) {
			fail();
		}
	}
	
	@Test
	public void getByRouteTest() {
		List<Trip> trips = tD.getAll();
		assertNotNull(trips);
		List<Route> routes = rD.getAll();
		assertNotNull(routes);
		for(Route route : routes) {
			List<Trip> tByR = tD.getByRoute(route);
			assertNotNull(tByR);
			if (tByR.size() != 0) {
				for(Trip trip : tByR) {
					assertTrue(trip.getRoute().equals(route));
				}
			}
			else {
				for(Trip trip : trips) {
					assertFalse(trip.getRoute().equals(route));
				}
			}
		}
	}
	
	@Test
	public void getByTripTest() {
		List<Person> persons = pD.getAll();
		assertNotNull(persons);
		List<Trip> trips = tD.getAll();
		assertNotNull(trips);
		List<Person_trip> pts = ptD.getAll();
		assertNotNull(pts);
		for(Person person : persons) {
			List<Trip> tByP = tD.getByPerson(person);
			assertNotNull(tByP);
			if (tByP.size() != 0) {
				for(Trip trip : tByP) {
					boolean found = false;
					for(Person_trip pt : pts) {
						if (pt.getPerson().equals(person) && pt.getTrip().equals(trip)) {
							found = true;
							break;
						}
					}
					assertTrue(found);
				}
			} 
			else {
				boolean found = false;
				for(Person_trip pt : pts) {
					if (pt.getTrip().equals(person)) {
						found = true;
						break;
					}
				}
				assertFalse(found);
			}
		}
	}
}
